﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using JigLibX.Collision;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Math;

namespace DARE
{
    /// <summary>
    /// This class create a collision mesh from a model.
    /// The model has to be in one mesh part for the moment.
    /// </summary>
    public class CPhysicMesh : APhysicObject
    {

    #region fields

        private CModel m_model;
        private List<Vector3> m_vertices;
        private List<TriangleVertexIndices> m_indices;
        private TriangleMesh m_triangleMesh;
        private bool m_includeNoncoll;

        private int m_numVertices;
        private int m_numIndices;
        private VertexBuffer m_vertexBuffer;
        private IndexBuffer m_indexBuffer;

    #endregion

    #region properties

        public bool IncludeNoncoll { get { return m_includeNoncoll; } }

        public CModel Model
        {
            get { return m_model; }
        }

        public List<Vector3> Vertices
        {
            get { return m_vertices; }
        }

        public List<TriangleVertexIndices> Indices
        {
            get { return m_indices; }
        }

        public TriangleMesh TriangleMesh
        {
            get { return m_triangleMesh; }
        }

    #endregion

    #region ctor

        public CPhysicMesh(CModel model)
            : this(model, false)
        {

        }

        public CPhysicMesh(CModel model, bool includeNoncoll)
        {
            Initialize(model, includeNoncoll);
        }

        //public CPhysicMesh(Model model, bool includeNoncoll = false)
        //{
            //Initialize(model, includeNoncoll);
        //}

        private void Initialize(CModel model, bool includeNoncoll)
        {
            if (model == null)
                throw new Exception("The model must be valid to create a physic mesh !");
            Vector3 position = Vector3.Zero;
            Matrix orientation = Matrix.Identity;

            m_model = model;
            m_includeNoncoll = includeNoncoll;
            m_vertices = new List<Vector3>();
            m_indices = new List<TriangleVertexIndices>();
            m_vertices.Clear();
            m_indices.Clear();
            ExtractData(m_model.Model, ref m_vertices, ref m_indices, includeNoncoll);

            m_body = new Body();
            //m_collisionSkin = new CollisionSkin();
            m_collisionSkin = new CollisionSkin(m_body);
            m_body.CollisionSkin = m_collisionSkin;
            m_triangleMesh = new TriangleMesh();

            // some display
            Console.WriteLine(m_vertices.Count.ToString() + " " + m_indices.Count.ToString());

            m_triangleMesh.CreateMesh(m_vertices, m_indices, 4, 0.2f);
            m_collisionSkin.AddPrimitive(m_triangleMesh, new MaterialProperties(0.8f, 0.7f, 0.6f));
            PhysicsSystem.CurrentPhysicsSystem.CollisionSystem.AddCollisionSkin(m_collisionSkin);

            m_collisionSkin.ApplyLocalTransform(new JigLibX.Math.Transform(position, orientation));
            m_body.MoveTo(position, orientation);

            // --------------------- basic model creation ----------------------
            VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[m_numVertices];
            //short[] indices = new short[m_numVertices];
            short[] indices = new short[m_numIndices];

            Console.WriteLine(vertices.Length.ToString() + " " + m_numIndices.ToString());

            m_vertexBuffer.GetData<VertexPositionNormalTexture>(vertices);
            //m_indexBuffer.GetData<short>(indices);

            for (int i = 0; i < vertices.Length; ++i)
                vertices[i].Position *= m_model.Scale;

            // why ?
            //m_model.Scale = Vector3.One;

            m_vertexBuffer = new VertexBuffer(CDare.Instance.GraphicsDevice, typeof(VertexPositionNormalTexture), vertices.Length, BufferUsage.WriteOnly);
            m_vertexBuffer.SetData<VertexPositionNormalTexture>(vertices);
            //m_indexBuffer.SetData<short>(indices);

            //m_basicModel = new CBoxModel("box");
            m_basicModel = new CTriangleMeshModel(m_model.Name, vertices, indices);
        }

        public override APhysicObject Clone()
        {
            CPhysicMesh clone = new CPhysicMesh(m_model, m_includeNoncoll);

            base.CopyPropertiesTo(clone);
            return clone;
        }
        
    #endregion

    #region methods

        public void ExtractData(Model mdl, ref List<Vector3> vtcs, ref List<TriangleVertexIndices> idcs, bool includeNoncoll)
        {
            Matrix m = Matrix.Identity;

            Console.WriteLine("ExtractData");
            foreach (ModelMesh mm in mdl.Meshes)
            {
                m = GetAbsoluteTransform(mm.ParentBone);
                ExtractModelMeshData(mm, ref m, ref vtcs, ref idcs, "Collision Model", includeNoncoll);
            }

            //// some display:
            //Console.WriteLine("Vertices: " + m_vertices.Count.ToString());
            //for (int i = 0; i < m_vertices.Count; ++i)
            //    Console.WriteLine("vertices[" + i.ToString() + "] = " + m_vertices[i].X.ToString() + " " + 
            //        m_vertices[i].Y.ToString() + " " + m_vertices[i].Z.ToString());
            //Console.WriteLine("Indices: " + m_indices.Count.ToString());
            //for (int i = 0; i < m_indices.Count; ++i)
            //    Console.WriteLine("indices[" + i.ToString() + "] = " + m_indices[i].I0.ToString() + " " + 
            //        m_indices[i].I1.ToString() + " " + m_indices[i].I2.ToString());
        }
 
        public Matrix GetAbsoluteTransform(ModelBone bone)
        {
            return bone == null ? Matrix.Identity : bone.Transform * GetAbsoluteTransform(bone.Parent);
        }
 
        public void ExtractModelMeshData(ModelMesh mm, ref Matrix xform, 
            ref List<Vector3> vertices, ref List<TriangleVertexIndices> indices, string name, bool includeNoncoll)
        {
            Console.WriteLine("ExtractModelMeshData");
            foreach (ModelMeshPart mmp in mm.MeshParts)
            {
                if (!includeNoncoll)
                {
                    EffectAnnotation annot = mmp.Effect.CurrentTechnique.Annotations["collide"];

                    if (annot != null && annot.GetValueBoolean() == false)
                    {
                        Console.WriteLine("Ignoring model mesh part {0}:{1} because it's set to not collide.",
                            name, mm.Name);
                        continue;
                    }
                }
                ExtractModelMeshPartData(mm, mmp, ref xform, ref vertices, ref indices, name);
            }
        }
 
        public void ExtractModelMeshPartData(ModelMesh mm, ModelMeshPart mmp, ref Matrix xform,
            ref List<Vector3> vertices, ref List<TriangleVertexIndices> indices, string name)
        {
            Console.WriteLine("ExtractModelMeshPartData");
            int offset = vertices.Count;
            //Vector3[] a = new Vector3[mmp.NumVertices];
            Vector3[] a = new Vector3[mmp.VertexBuffer.VertexCount];
            VertexPositionNormalTexture[] b = new VertexPositionNormalTexture[mmp.VertexBuffer.VertexCount];

            // get the vertex and index buffer, should create one instead and add indices / vertices
            m_vertexBuffer = mmp.VertexBuffer;
            m_indexBuffer = mmp.IndexBuffer;
            //m_numVertices = mmp.NumVertices;
            m_numVertices = mmp.VertexBuffer.VertexCount;
            m_numIndices = mmp.IndexBuffer.IndexCount;

            //mmp.VertexBuffer.GetData<Vector3>(mmp.VertexOffset,
                //a, 0, mmp.NumVertices, mmp.VertexBuffer.VertexDeclaration.VertexStride);
            // mmp.VertexBuffer.GetData<Vector3>(a);
            mmp.VertexBuffer.GetData<VertexPositionNormalTexture>(b);
            // copy in a new array to not change the vertex buffer for all the other same objects
            for (int i = 0; i < b.Length; ++i)
                a[i] = new Vector3(b[i].Position.X, b[i].Position.Y, b[i].Position.Z);
            // ---------------------------------------------------------------------------
            //for (int i = 0; i != a.Length; ++i)
                //Vector3.Transform(ref a[i], ref xform, out a[i]);
            vertices.AddRange(a);

            // apply scale
            for (int i = 0; i < vertices.Count; ++i)
                vertices[i] *= m_model.Scale;// * 0.01f;

            if (mmp.IndexBuffer.IndexElementSize != IndexElementSize.SixteenBits)
                throw new Exception(String.Format("Model {0} uses 32-bit indices, which are not supported.", name));
            //short[] s = new short[mmp.PrimitiveCount * 3];
            //mmp.IndexBuffer.GetData<short>(mmp.StartIndex * 2, s, 0, mmp.PrimitiveCount * 3);
            short[] s = new short[m_numIndices];
            mmp.IndexBuffer.GetData<short>(s);
            //offset = 0;
            // ---------------------------------------------------------------------------

            //TriangleVertexIndices[] tvi = new TriangleVertexIndices[mmp.PrimitiveCount];
            TriangleVertexIndices[] tvi = new TriangleVertexIndices[m_numIndices / 3];
            for (int i = 0; i != tvi.Length; ++i)
            {
                tvi[i].I0 = s[i * 3 + 0] + offset;
                tvi[i].I1 = s[i * 3 + 1] + offset;
                tvi[i].I2 = s[i * 3 + 2] + offset;
            }
            indices.AddRange(tvi);
        }

    #endregion

    }
}
